#!/usr/bin/env python

"""HgMon.py -- Monitoring directory change and sync project directories upon
request.

If CHANGED.FLG is in the project directory, the script will try to commit it,
then sync (hg push) to other directories of the same project. If any conflict
files are found, it will create a branch named by date and time, commit the
conflicts to the branch, then branch to default and update the previous push
content.

If LOCKED.FLG is in the project directory, the script will ignore the directory,
no change will be pushed or synchronized to the project.

Content format of CHANGED.FLG is "User Message String", e.g.,
    Joel Changed something.

The first word in the message is treated as user name, other strings are treated
as commit messages.
    
"""

import os
import time
import yaml
import logging
import logging.handlers
from datetime import datetime
from subprocess import Popen, PIPE
# from functools import wraps
from mercurial import ui, hg, commands

def confirm_project(project_dir):
    "Make sure project directory actually exists and create one if it doesn't."
    if os.path.exists(project_dir):
        if not os.path.isdir(project_dir): 
            try: shutil.rmtree(project_dir)
            except:
                print "Unable to remove non-project directory %s" % project_dir
            try:
                os.makedirs(project_dir)
            except:
                print "Unable to create project directory %s" % project_dir
    else:   # path not exist
        try: os.makedirs(project_dir)
        except:
            print "Unable to create project directory %s" % project_dir

def parse_flag(chflag):
    "change flag file -> user, comment"
    with open(chflag, 'r') as fh:
        user, comment = tuple(
            fh.readline().strip().split(' ', 1))
    return user, comment

def auto_branch_name():
    "generates branch name based on datetime.now()"
    now = datetime.now()
    d, t = now.date(), now.time()
    dstr = str(d.year)[2:] + str(d.month).zfill(2) + str(d.day).zfill(2)
    tstr = str(t.hour).zfill(2) + str(t.minute).zfill(2) + str(t.second).zfill(2)
    return dstr + '/' + tstr
    
def modify_config(project_dir):
    "Writes config to do auto update after hg push."
    config_file = os.path.join(project_dir, '.hg', 'hgrc')
    with open(config_file, 'a') as cfg:
        cfg.write('[hooks]\n')
        cfg.write('changegroup = hg update >&2\n')

def loadconfig(configfile):
    "Project_dir_group_config_file -> dir group list"
    with open(configfile) as f:
        y = yaml.load(f)
    grouplist = []
    for entry in y:
        for k, v in entry.iteritems():
            sdirs = v['syncdirs'].split()
            pname = v['prjname']
            grouplist.append(set(map(lambda x: os.path.join(x, pname), sdirs)))
    return grouplist

def hg_init(dest):
    return commands.init(ui.ui(), dest=dest)

def hg_commit(u, dest, comment):
    repo = hg.repository(u, dest)
    return commands.commit(u, repo, message=comment, addremove=True)

def hg_push(u, src, dest):
    src_repo = hg.repository(u, src)
    dest_repo = hg.repository(u, dest)
    return commands.push(u, src_repo, dest, force=True)

def hg_update_branch(u, dest, br_name=None):
    repo = hg.repository(u, dest)
    return commands.update(u, repo, node=br_name)

def hg_update_c(u, dest):
    repo = hg.repository(u, dest)
    return commands.update(u, repo, check=True)

def hg_status_is_modified(u, dest):
    repo = hg.repository(u, dest)
    return not commands.status(u, repo)
    # Patched commands.py to get this working.
    # Appended the two lines in module function commands.status()
    ## if len(filter(lambda x: len(x)!=0, stat)) == 0: return True
    ## else: return False
    # I heard it's always good to return something in your function.
    
def hg_branch(u, dest, br_name):
    repo = hg.repository(u, dest)
    return commands.branch(u, repo, label=br_name)

def hg_autobranch(u, dest):
    if hg_status_is_modified(u, dest):
        hg_branch(u, dest, auto_branch_name())
        hg_commit(u, dest, 'auto commit')
        return hg_update_branch(u, dest, br_name='default')
    else:
        hg_update_branch(u, dest, br_name='default')

if __name__ == '__main__':
    configfile = 'config.yaml'
    flagfile = 'CHANGED.FLG'
    lockfile = 'LOCK.FLG'
    while True:
        grouplist = loadconfig(configfile)
        for group in grouplist:
            for project_dir in group:
                confirm_project(project_dir)
                chflag = os.path.join(project_dir, flagfile)
                hgflag = os.path.join(project_dir, '.hg')
                lockfile = os.path.join(project_dir, lockfile)
                if os.path.exists(lockfile): continue
                if not os.path.exists(hgflag):
                    hg_init(project_dir)
                if os.path.exists(chflag):
                    time.sleep(0.2)
                    user, comment = parse_flag(chflag)
                    os.environ['HGUSER'] = user
                    u = ui.ui()
                    os.remove(chflag)
                    hg_commit(u, project_dir, comment)
                    syncgroup = group.copy()
                    syncgroup.remove(project_dir)
                    for syncdir in syncgroup:
                        if os.path.exists(lockfile):
                            continue
                        hg_push(u, project_dir, syncdir)
                        hg_autobranch(u, syncdir)
        time.sleep(10)
